Re: SUMMARY Security Info (root broken)

Casper Dik (casper@fwi.uva.nl)
Tue, 04 Oct 1994 10:24:51 +0100

>I think that Pat has highlighted here the problem with a lot of
>packages that use a setuid root process to create a file in a
>restricted directory (e.g, a 775 root.mail /var/spool/mail.)
>
>I've looked at the 4.4BSD-lite (NetBSD uses this) mail.local.c and at
>first, thought there was a potential race condition in the code
>where it does an lstat check then an open, thinking there was a race
>condition. Checking the man page for open() however, revealed the
>following tidbits:
>	If path is a symbolic link and O_CREAT and O_EXCL are set,
>	 the link is not followed.
>(From Solaris 2.3, and the NetBSD-current man page says something
>similar.)

These semntics were introduced at the same time that symlinks were
introduced.  However, some vendors did pick up symlinks but w/o
the don't follow on O_CREAT|O_EXCL semantics.  (IRIX 4.0.x, I think)

>So, it seems that a standard (POSIX?) has explicitly given us a method
>to atomically create a file if it doesn't exist, whilst at the same
>time not getting fooled by a dangling symlink (which is a common way
>to exploit setuid race conditions, correct?)

POSIX doesn't specify symlinks (yet).

>Now, I don't know if this helps people on systems where this behaviour
>doesn't exist (I'm not sure if Sunos 4 supports this, for example.)

SunOS 4 and all BSD/SVR4 derived systems should.  SVR3 and earlier with
symlinks need not.

>It's the creating of the new file by a priviliged process that
>is the critical region that so often gets spoofed by a race
>condition.  I have some (simple - thus easy to follow and assure
>is correct - I hope :) code at home that I was working on which should
>work without a race condition (using the atomic link()), so I'll
>post it tomorrow to get disected by those with more experience than
>I. If it does work the way I expect it to, I feel that a simpler,
>more effective, mail.local could be implemented that didn't rely upon
>the O_CREAT | O_EXCL feature of newer systems I described above...

Here's some code I wrote earlier that does much the same thing.
(it dates from May somewhere)

Casper

#! /bin/sh
# This is a shell archive.  Remove anything before this line, then unpack
# it by saving it into a file and typing "sh file".  To overwrite existing
# files, type "sh file -c".  You can also feed this as standard input via
# unshar, or by typing "sh <file", e.g..  If this archive is complete, you
# will see the following message at the end:
#		"End of shell archive."
# Contents:  safecreate.c
# Wrapped by casper@fwi.uva.nl on Tue May 17 16:34:58 1994
PATH=/bin:/usr/bin:/usr/ucb ; export PATH
if test -f 'safecreate.c' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'safecreate.c'\"
else
echo shar: Extracting \"'safecreate.c'\" \(3836 characters\)
sed "s/^X//" >'safecreate.c' <<'END_OF_FILE'
X/*
X * Safe way to create/open a file in a sticky directory.
X * the file must belong to the executing user.
X *
X * Casper Dik (casper@fwi.uva.nl)
X */
X#include <stdio.h>
X#include <stdlib.h>
X#include <unistd.h>
X#include <sys/stat.h>
X#include <sys/types.h>
X#include <sys/file.h>
X#include <string.h>
X#include <errno.h>
X#include <pwd.h>
X
X#ifndef FILEMODE
X#define FILEMODE 0600
X#endif
X#ifndef FILEGROUP
X#define FILEGROUP -1
X#endif
X#define FIXMODE
X
XFILE *
Xopenmbox(uid_t, char *);
X
Xmain(int argc, char **argv)
X{
X    struct passwd *pwd;
X    FILE *mbox;
X    int c;
X
X    if (argc != 3) {
X	fprintf(stderr,"Usage: %s file user\n", argv[0]);
X	exit(1);
X    }
X    pwd = getpwnam(argv[2]);
X    if (pwd == 0) {
X	fprintf(stderr,"%s: %s: unknown user\n", argv[0], argv[1]);
X	exit(1);
X    }
X    if ((mbox = openmbox(pwd->pw_uid, argv[1])) == 0) {
X	fprintf(stderr,"Couldn't safely open %s\n", argv[1]);
X	exit(1);
X    }
X    while((c = getchar()) != EOF)
X	putc(c, mbox);
X    exit(0);
X}
X
XFILE *
Xopenmbox(uid_t uid, char *file)
X{
X    char *dir, *p;
X    int fd;
X    FILE *f;
X
X    dir = strdup(file);
X    p = strrchr(dir,'/');
X    if (!p)
X	dir = ".";
X    else
X	*p = '\0';
X
X    /* First we'll try a normal open, no O_CREAT */
X    fd = open(file, O_WRONLY|O_APPEND);
X    if (fd == -1) {
X	char *tmpf;
X
X	/* If there's another error, we can't handle it */
X	if (errno != ENOENT) {
X	    perror(file);
X	    return 0;
X	}
X
X	/* Now we must create the file; */
X	tmpf = tempnam(dir, "psrz");
X	if (tmpf == 0) {
X	    fprintf(stderr,"Can't generate unique filename\n");
X	    return 0;
X	}
X	fd = open(tmpf, O_WRONLY|O_APPEND|O_CREAT|O_EXCL, FILEMODE);
X	if (fd < 0) {
X	    fprintf(stderr,"Can't create temporary file\n");
X	    (void) unlink(tmpf);
X	    free(tmpf);
X	    return 0;
X	}
X	/* Now we have a file in the spool directory, we're going to
X	 * rename it the old fashioned way because we want it to
X	 * fail if someone created the mailbox in the meantime */
X	if (link(tmpf, file) == -1) {
X	    if (errno == EEXIST)
X		fprintf(stderr,"%s created behind our back\n", file);
X	    else
X		perror("link");
X	    (void) unlink(tmpf);
X	    free(tmpf);
X	    return 0;
X	}
X	(void) unlink(tmpf);
X	free(tmpf);
X	/*
X	 * At this point we have the file and an open filedescriptor
X	 * We can be sure we aren't overwriting another file because we've
X	 * created this file in the spool directory.
X	 * Only on systems where O_CREAT|O_EXCL creates files at the end
X	 * of symlinks we have to worry */
X    } else {
X	struct stat fdbuf, filebuf;
X
X	/* the next two things should never happen */
X	if (fstat(fd, &fdbuf) == -1) {
X	    perror("fstat");
X	    return 0;
X	}
X	if (lstat(file, &filebuf) == -1) {
X	    perror(file);
X	    return 0;
X	}
X
X	/* Now check that: file and fd reference the same file,
X	   file only has one link, file is plain file */
X	if (fdbuf.st_dev != filebuf.st_dev ||
X	    fdbuf.st_ino != filebuf.st_ino ||
X	    fdbuf.st_nlink != 1 ||
X	    filebuf.st_nlink != 1 ||
X	    (fdbuf.st_mode & S_IFMT) != S_IFREG) {
X	    fprintf(stderr,"%s must be a plain file with one link\n", file);
X	    return 0;
X	}
X	/* we have a filedescriptor pointing to a file in the spool directory
X	 * how can we be sure it wasn't pointing at another file when we
X	 * opened it?
X	 * At the time of the lstat, the file in the spool directory was
X	 * a hardlink to the file we previously opened.
X	 * (st_dev and st_ino uniquely identify a file)
X	 * It was the only hardlink.
X	 * Could it have been a different file previously?
X	 * Yes, but only if the file existed in a writable directory and
X	 * no longer exists there now.
X	 */
X    }
X    /* Now we have an fd pointing to the file in the spool directory.
X     * Now we're going to fix ownership and mode */
X#ifdef FIXMODE
X    (void) fchmod(fd, FILEMODE);
X    (void) fchown(fd, uid, FILEGROUP);
X#endif
X    f = fdopen(fd, "a");
X    if (f == 0)
X	close(fd);
X    return f;
X}
END_OF_FILE
if test 3836 -ne `wc -c <'safecreate.c'`; then
    echo shar: \"'safecreate.c'\" unpacked with wrong size!
fi
# end of 'safecreate.c'
fi
echo shar: End of shell archive.
exit 0